package com.xinchao.ck_mips.utils.download;

import android.text.TextUtils;
import android.util.Patterns;

import com.bumptech.glide.load.HttpException;
import com.xinchao.ck_mips.utils.task.Task;
import com.xinchao.ck_mips.utils.task.TaskManager;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.security.MessageDigest;
import java.security.cert.CertificateException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/**
 * Created by zhangbw on 2018/8/21.
 */

public class OkhttpDownloadTools {

    private static ExecutorService sExecutorService;
    private static final String TEMP_SUFFIX = ".tmp";
    private static ConcurrentLinkedQueue<OkHttpClient> sOkHttpClients;
    private static ConcurrentHashMap<String, OkHttpDownloadListener> sConcurrentHashMap;

    static {
        sConcurrentHashMap = new ConcurrentHashMap<>();
        sExecutorService = Executors.newCachedThreadPool();
        sOkHttpClients = new ConcurrentLinkedQueue<>();
    }

    /**
     * @param url              下载地址
     * @param fileName         文件保存地址
     * @param downloadListener 下载监听
     * @param isCheckPoint     是否开启断点续传
     * @return
     */
    public static Call download(final String url, final String fileName, final boolean isCheckPoint, final OkHttpDownloadListener downloadListener) {
        if (!Patterns.WEB_URL.matcher(url).matches()) {
            return null;
        }
        long tempStart = 0;
        if (isCheckPoint) {//如果是断点续传，需要检查文件是否合法
            long fileStart = getFileStart(url, fileName);
            if (fileStart > 0) {
                long fileLen = getRemoteFileLen(url);
                if (fileLen != 0) {
                    if (isSameFileLen(fileLen, fileName)) {
                        tempStart = fileStart > 0 ? fileStart - 1 : fileStart;
                    }
                }
            }
        }
        final long startsPoint = tempStart;
        final boolean checkPoint = isCheckPoint;
        sConcurrentHashMap.put(url, downloadListener);
        return download(url, downloadListener, startsPoint, isCheckPoint, new DownloadCallBack(url, fileName, checkPoint, startsPoint));
    }

    /**
     * 下载文件
     *
     * @param url
     * @param downloadListener
     * @param startsPoint
     * @param callback
     * @param isCheckPoint     是否开启断点续传
     * @return
     */
    private static Call download(final String url, final OkHttpDownloadListener downloadListener, final long startsPoint, boolean isCheckPoint, final DownloadCallBack callback) {

        Request.Builder builder = new Request.Builder()
                .url(url);

        if (isCheckPoint) {
            builder.header("RANGE", "bytes=" + startsPoint + "-");//断点续传
        }

        // 重写ResponseBody监听请求
        Interceptor interceptor = chain -> {
            Response originalResponse = chain.proceed(chain.request());
            return originalResponse.newBuilder()
                    .body(new DownloadResponseBody(originalResponse, startsPoint, downloadListener))
                    .build();
        };

        OkHttpClient.Builder dlOkhttp = getUnsafeOkHttpClient();
        dlOkhttp.addNetworkInterceptor(interceptor);
        OkHttpClient okHttpClient = dlOkhttp.build();
        sOkHttpClients.add(okHttpClient);
        // 发起请求
        final Call call = okHttpClient.newCall(builder.build());
        callback.setCall(call);
        callback.setHttpClient(okHttpClient);
        TaskManager.getInstance().addTask(callback);
        return call;
    }

    /**
     * 停止所有下载
     */
    public static void stopAllDownload() {
        for (OkHttpClient okHttpClient : sOkHttpClients) {
            okHttpClient.dispatcher().cancelAll();
        }
        sOkHttpClients.clear();
    }

    /**
     * 跳过ssl 验证
     */
    private static OkHttpClient.Builder getUnsafeOkHttpClient() {
        OkHttpClient.Builder dlOkhttp = new OkHttpClient.Builder();
        try {
            final TrustManager[] trustAllCerts = new TrustManager[]{
                    new X509TrustManager() {
                        @Override
                        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                        }

                        @Override
                        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                        }

                        @Override
                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                            return new java.security.cert.X509Certificate[]{};
                        }
                    }
            };
            final SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
            final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

            dlOkhttp.sslSocketFactory(sslSocketFactory);
            dlOkhttp.hostnameVerifier((hostname, session) -> true);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dlOkhttp;
    }

    private static File getFile(String fileName) {
        File file = new File(fileName);
        return file;
    }

    /**
     * 获取下载文件的长度
     *
     * @param url
     * @return
     */
    private static long getRemoteFileLen(String url) {
        Request request = new Request.Builder()
                .url(url)
                .build();
        final long[] fileLen = new long[1];
        final Object object = new Object();
        synchronized (object) {
            getUnsafeOkHttpClient().build().newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    synchronized (object) {
                        fileLen[0] = 0L;
                        object.notify();
                    }
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    synchronized (object) {
                        fileLen[0] = response.body().contentLength();
                        object.notify();
                    }
                }
            });
            try {
                object.wait(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return fileLen[0];
        }
    }

    /**
     * 获取文件头信息
     *
     * @param url
     * @param fileName
     * @return
     */
    private static long getFileStart(String url, String fileName) {
        long start = 0;
        String tempMd5 = getTemp(fileName + TEMP_SUFFIX);
        if (!TextUtils.isEmpty(tempMd5) && tempMd5.contains(",")) {
            String[] tmp = tempMd5.split(",");
            if (tmp.length == 2) {
                if (TextUtils.equals(backMD5(url), tmp[0])) {
                    start = getFile(fileName).length();
                }
            }
        }
        return start;
    }

    /**
     * 判断文件长度与缓存是不是相等
     *
     * @param fileLen
     * @param fileName
     * @return
     */
    private static boolean isSameFileLen(long fileLen, String fileName) {
        boolean isSame = false;
        String tempMd5 = getTemp(fileName + TEMP_SUFFIX);
        if (!TextUtils.isEmpty(tempMd5) && tempMd5.contains(",")) {
            String[] tmp = tempMd5.split(",");
            if (tmp.length == 2) {
                try {
                    isSame = fileLen == Long.parseLong(tmp[1]);
                } catch (Exception e) {

                }
            }
        } else {
            isSame = true;
        }
        return isSame;
    }

    /**
     * 写入摘要
     *
     * @param fileName 文件名称
     * @param content  文件内容
     * @throws Exception
     */

    private static void saveTemp(String fileName, String content) {
        try {
            File newFile = new File(fileName + TEMP_SUFFIX);

            if (newFile.exists() && newFile.isFile()) {
                newFile.delete();
            }
            newFile.createNewFile();
            FileWriter fw = new FileWriter(fileName + TEMP_SUFFIX, false);
            fw.write(content);
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取摘要
     *
     * @param file
     * @return
     */
    private static String getTemp(String file) {
        StringBuffer sb = new StringBuffer("");
        if (isFileExist(file)) {
            try {
                BufferedReader buf = new BufferedReader(new FileReader(file));
                String line;
                while ((line = buf.readLine()) != null) {
                    sb.append(line);
                }
                buf.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sb.toString();
    }

    /**
     * 文件是不是存在
     *
     * @param filePath
     * @return
     */
    private static boolean isFileExist(String filePath) {
        boolean result = false;
        File file = new File(filePath);
        if (file != null && file.exists() && file.isFile()) {
            result = true;
        }
        return result;
    }

    /**
     * 字符串md5值计算
     *
     * @param inStr
     * @return
     */
    private static String backMD5(String inStr) {

        MessageDigest md5;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            return "";
        }

        char[] charArray = inStr.toCharArray();
        byte[] byteArray = new byte[charArray.length];

        for (int i = 0; i < charArray.length; i++)
            byteArray[i] = (byte) charArray[i];

        byte[] md5Bytes = md5.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();

        for (int i = 0; i < md5Bytes.length; i++) {

            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16)
                hexValue.append("0");
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }

    /**
     * 删除文件
     *
     * @param fileName
     */
    private static void deleteFile(String fileName) {
        if (isFileExist(fileName)) {
            new File(fileName).delete();
        }
    }

    static class DownloadCallBack extends Task {

        String mDownloadUrl;
        String mFileName;
        boolean mCheckPoint;
        long mStartPoint;
        Call mCall;
        OkHttpClient mOkHttpClient;

        public DownloadCallBack(String downloadUrl, String fileName, boolean checkPoint, long startPoint) {
            mDownloadUrl = downloadUrl;
            mFileName = fileName;
            mCheckPoint = checkPoint;
            mStartPoint = startPoint;
        }

        public void onFailure(Call call, IOException e) {
            call.cancel();
            OkHttpDownloadListener downloadListener = sConcurrentHashMap.remove(mDownloadUrl);
            if (downloadListener != null) {
                downloadListener.onDownloadFailed(e);
            }
        }

        public void onResponse(Call call, Response response) throws IOException {
            long length = response.body().contentLength();
            OkHttpDownloadListener downloadListener = sConcurrentHashMap.remove(mDownloadUrl);
            if (length == 0) {
                // 说明文件已经下载完
                if (downloadListener != null) {
                    downloadListener.onDownloadSuccess(String.valueOf(getFile(mFileName).getAbsoluteFile()));
                }
                return;
            }

            if (length == -1) {
                // 说明文件已经下载完
                if (downloadListener != null) {
                    downloadListener.onDownloadFailed(new FileNotFoundException(mDownloadUrl));
                }
                return;
            }

            if (mCheckPoint) {
                saveTemp(mFileName, backMD5(mDownloadUrl) + "," + (length + mStartPoint));
            }
            if (downloadListener != null) {
                downloadListener.onStart(length + mStartPoint);
            }
            // 保存文件到本地
            InputStream is = null;
            RandomAccessFile randomAccessFile = null;
            BufferedInputStream bis = null;

            byte[] buff = new byte[2048];
            int len;
            try {
                is = response.body().byteStream();
                bis = new BufferedInputStream(is);

                File file = getFile(mFileName);
                if (!mCheckPoint && file.exists() && file.isFile()) {
                    file.delete();
                }
                // 随机访问文件，可以指定断点续传的起始位置
                randomAccessFile = new RandomAccessFile(file, "rwd");
                randomAccessFile.seek(mStartPoint);
                while ((len = bis.read(buff)) != -1) {
                    randomAccessFile.write(buff, 0, len);
                }

                // 下载完成
                if (downloadListener != null) {
                    downloadListener.onDownloadSuccess(String.valueOf(file.getAbsoluteFile()));
                }
                deleteFile(mFileName + TEMP_SUFFIX);
            } catch (Exception e) {
                if (downloadListener != null) {
                    downloadListener.onDownloadFailed(e);
                }
            } finally {
                try {
                    if (is != null) {
                        is.close();
                    }
                    if (bis != null) {
                        bis.close();
                    }
                    if (randomAccessFile != null) {
                        randomAccessFile.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                call.cancel();
            }
        }

        @Override
        public void onRun() {
            if (mCall != null) {
                try {
                    Response response = mCall.execute();
                    if (response.isSuccessful()) {
                        onResponse(mCall, response);
                    } else {
                        onFailure(mCall, new HttpException(response.code()));
                    }
                } catch (IOException e) {
                    onFailure(mCall, e);
                }
            }
        }

        @Override
        public void onCancel() {
            if (mOkHttpClient != null) {
                mOkHttpClient.dispatcher().cancelAll();
            }
        }

        public void setCall(Call call) {
            mCall = call;
        }

        public void setHttpClient(OkHttpClient client) {
            mOkHttpClient = client;
        }
    }
}
